import * as _ from 'shenjian-tools/src';

import defaultOptions from './configs';
import { assign, extractFields } from './utils';
import {
  Configs,
  cbSite,
  cbPageStrSite,
  cbAfterExtractField,
  cbAfterExtractPage,
} from './types';

class Creeper {
  private configs: Configs;

  constructor(configs: Configs) {
    const fields: any = [];

    // 合并字段配置
    if (configs.fields instanceof Array) {
      const map: any = {};
      defaultOptions.fields.forEach(it => {
        const item = assign({}, it);
        fields.push(item);
        map[it.name] = item;
      });
      configs.fields.forEach(it => {
        if (map[it.name]) {
          assign(map[it.name], it);
        } else {
          fields.push(it);
        }
      });
    }

    this.configs = assign(defaultOptions, configs, { fields });
  }

  /**
   * 爬虫初始化的时候被调用，多节点运行时，只在第一个节点（又称主节点）中被调用，其他节点等待主节点的initCrawl方法执行完之后才继续往下执行。
   *
   * 注意：建议在此回调中做添加入口页的操作。
   */
  public initCrawl(fn: cbSite): void {
    this.configs.initCrawl = fn;
  }

  /**
   * 网页在下载完之后，如果当前链接是入口页，则回调此函数。一般在此函数中实现手动链接发现，一般是发现帮助页，也可以直接发现内容页。
   */
  public onProcessScanPage(fn: cbPageStrSite): void {
    this.configs.onProcessScanPage = fn;
  }

  /**
   * 在每个抽取项抽取到内容时回调此函数，一个网页的抽取过程中，会多次回调此函数。在此函数中，可以对抽取到的数据做进一步的处理，然后返回处理后的数据。
   */
  public afterExtractField(fn: cbAfterExtractField): void {
    this.configs.afterExtractField = fn;
  }
  /**
   * 当整个网页完成抽取时回调此函数。一般在此回调中做一些数据整理的操作，也可以继续发送网络请求，把返回的数据整理后放到data中返回。
   */
  public afterExtractPage(fn: cbAfterExtractPage): void {
    this.configs.afterExtractPage = fn;
  }

  /**
   * 启动
   */
  public start() {
    this.automatedProcessing();
    return new Crawler(this.configs as any).start();
  }

  /**
   * 一些自动化处理
   */
  private automatedProcessing() {
    // 自动抽取字段
    if (!this.configs.afterExtractField) {
      this.configs.afterExtractField = (fieldName, data, page, site, index) => {
        return extractFields(fieldName, data, page, site, index);
      };
    }

    // 抽取后计算指定字段
    if (!this.configs.afterExtractPage) {
      this.configs.afterExtractPage = (page, data: any) => {
        data.length = _.strLen(data.content); // 文章字数
        data.deep = _.helper.isDeep(data.length) ? 1 : 0; // 是否深度文章
        return data;
      }
    }
  }
}

/**
 * 免 new 函数
 */
export function creeper(options: Configs) {
  return new Creeper(options);
}

export { _, extractFields };

export default Creeper;
